//-----------------------------------------------------------------------------
//  Copyright (C) 2005-2008 Thomas S. Ullrich 
//
//  This file is part of the Solitaire Mahjong project.
//
//  This file may be used under the terms of the GNU General Public License.
//  This project is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License.
//  
//  Author: Thomas S. Ullrich
//  Last update: June 1, 2007
//-----------------------------------------------------------------------------
#include <iostream>
#include <QDir>
#include <QFile>
#include <QApplication>
#include <QStatusBar>
#include <QMessageBox>
#include <QTextStream> 
#include "smDefinitions.h"
#include "smTileLayout.h"
#include "smTileItem.h"
 
smTileLayout::smTileLayout()
{
    mRows = 0;
    mColumns = 0;
    mNumberOfLayers = 0;
    mValidLayoutLoaded = false;
}
 
smTileLayout::~smTileLayout() { /* noop */ }
 
bool smTileLayout::makeLayout(smTileLayoutId layoutId)
{
    //
    // Check path to directory containing the tile layouts.
    //
    QString path = qApp->applicationDirPath() + "/layouts";
    QDir dir(path);
    if (!dir.exists()) {
        path = qApp->applicationDirPath() + "/../layouts";
        dir.setPath(path);
    }
#if defined(Q_OS_MAC)   
    if (!dir.exists()) {
        path = qApp->applicationDirPath() + "/../Resources/layouts";
        dir.setPath(path);
    }
#endif
    if (!dir.exists()) {
        QMessageBox::critical( 0, "Solitaire Mahjong",
                         "Cannot find the directory holding the tile layouts.\n"
                         "Ceck your installation and reinstall if necessary.\n"
                         "Solitaire Mahjong will now exit.");
        exit(1);
    }
     
    //
    // Select the layout to load.
    // So far we have only one: turtle.
    //
    path += "/";
     
    switch (layoutId) {
    case turtle:
    default:
        path += smTileLayoutName[layoutId];
        path += ".txt";
        break;
    }
     
    //
    // Open the file
    //
    QFile theFile(path);
    if (!theFile.open(QIODevice::ReadOnly)) {
        QMessageBox::critical( 0, "Solitaire Mahjong",
			 "Cannot open the tile layout file.\n"
			 "Ceck your installation and reinstall if necessary.\n");
        return false;
    }
    QTextStream theStream(&theFile);
    if (theStream.status() != QTextStream::Ok) {
        QMessageBox::critical( 0, "Solitaire Mahjong",
			 "Cannot read the tile layout file.\n"
			 "Ceck your installation and reinstall if necessary.\n");
        return false;
    }
    
    //
    //  Read the file into a vector but strip
    //  comments etc.
    //
    vector<QString> theLines;
    QString line;
    while (!theStream.atEnd()) {
        line = theStream.readLine();
        if (theStream.atEnd()) break;
        line = line.trimmed();
        if (line.isNull() || line.isEmpty() || line.startsWith("#",Qt::CaseSensitive)) continue;
        theLines.push_back(line);
    }
    theFile.close();
    
    //
    // First entry MUST be the number of layers and the dimensions.
    //
    line = theLines[0];
    sscanf(line.toAscii(),"%d %d %d", &mNumberOfLayers, &mColumns, &mRows);
     
    if (!(mNumberOfLayers > 0 && mRows > 0 && mColumns > 0)) {
        QMessageBox::critical( 0, "Solitaire Mahjong",
                         "The tile layout file has the wrong format.\n"
                         "Cannot load layout.\n");
        return false;
    }
     
    //
    // Build the 3dim layout array:
    // mLayout[layer][col][row]
    // All elements set to 'isEmpty'.
    //
    vector<int> aRow(mRows, isEmpty);
    vector<vector<int> > a2dArray(mColumns, aRow);
    for (int k=0; k<mNumberOfLayers; k++) mLayout.push_back(a2dArray);
     
    //
    // Now we need to load the actual layout.
    // Gridsize = 100. Upper left corner is (0,0).
    //
    QString theRow;
    QChar c;
    int nTiles = 0;
    for (int l=0; l<mNumberOfLayers; l++) {
        for (int row=0; row<mRows; row++) {
            theRow = theLines[1+l*mRows+row];
            for (int col=0; col<mColumns; col++) {
                c = theRow.at(col);
                switch(c.unicode()) {
                   case 'x':
                   case 'X':
                       mLayout[l][col][row] = isTile;
                       nTiles++;
                       break;
                   case '>':
                       mLayout[l][col][row] = isXOffsetTile;
                       nTiles++;
                       break;
                   case '^':
                       mLayout[l][col][row] = isYOffsetTile;
                       nTiles++;
                       break;
                   case '%':
                       mLayout[l][col][row] = isXYOffsetTile;
                       nTiles++;
                       break;
                   case '0':
                   default:
                       break;
                }
            }
        }
    }
     
    //
    //  Make sure we got the required smMaxNumberOfTiles (144)
    //  tile positions. Otherwise stop and complain.
    //
    if (nTiles != smMaxNumberOfTiles) {
        QMessageBox::critical( 0, "Solitaire Mahjong",
                         "The tile layout file is corrupted or ill defined.\n"
                         "Make sure all 144 tiles are included in the layout."
                         "Cannot load requested layout.\n");
        return false;
    }
     
    mTopoLayout = mLayout; // mLayout get's overwritten with the indices
     
    mValidLayoutLoaded = true;
    return true;
}
 
void smTileLayout::asciiPrintLayout()
{
    if (mValidLayoutLoaded) {
        int il, ir, ic;
        for (il=0; il<mNumberOfLayers; il++) {
            for (ir=0; ir<mRows; ir++) {
                for (ic=0; ic<mColumns; ic++) {
                    cout << mLayout[il][ic][ir] << ' ';
                }
                cout << endl;
            }
            cout << endl;
        }
    }
    else
        cout << "No valid layout available." << endl;
}
 
QSize smTileLayout::layoutTiles(vector<smTileItem*>& theTiles, int shadowWidth, int shadowHeight)
{
    //
    //  Store tiles in layout and setup geometry and indices.
    //  This is where the tiles are placed in their final design.
    //  If something looks badly placed - this is where it is
    //  probably going wrong.
    //
     
    if (!mValidLayoutLoaded) {
        QMessageBox::critical( 0, "Solitaire Mahjong",
                         "smTileLayout::layoutTiles(): cannot layout tiles.\n"
                         "No valid layout loaded.");
        return QSize();
    }
    if (theTiles.size() == 0) {
        QMessageBox::critical( 0, "Solitaire Mahjong",
                         "smTileLayout::layoutTiles(): got no tiles to layout.\n"
                         "Tile vector has size == 0.");
        return QSize();
    }
     
    const int width = theTiles[0]->width()-abs(shadowWidth);    // width of tile face (w/o shadow)
    const int height = theTiles[0]->height()-abs(shadowWidth);  // height
    const int xBorder = width;       // space around layout
    const int yBorder = width;
    int il, ir, ic;
    int index = 0;
    int layerXOffset, layerYOffset;      // for the depth effect
    for (il=0; il<mNumberOfLayers; il++) {
        layerXOffset = -il*shadowWidth;
        layerYOffset = -il*shadowHeight;
        for (ir=0; ir<mRows; ir++) {
            for (ic=0; ic<mColumns; ic++) {
                switch (mLayout[il][ic][ir]) {
                    case isTile:
                        mLayout[il][ic][ir] = index;
                        theTiles[index]->setColumn(ic);
                        theTiles[index]->setRow(ir);
                        theTiles[index]->setLayer(il);
                        theTiles[index]->setPos(xBorder+ic*width+layerXOffset, yBorder+ir*height+layerYOffset);
                        theTiles[index]->setZValue(depth(il,ic,ir));
                        index++;
                        break;
                    case isXOffsetTile:
                        mLayout[il][ic][ir] = index;
                        theTiles[index]->setColumn(ic);
                        theTiles[index]->setRow(ir);
                        theTiles[index]->setLayer(il);
                        theTiles[index]->setXOffset(static_cast<int>(width/2.));
                        theTiles[index]->setPos(xBorder+static_cast<int>(ic*width+width/2.)+layerXOffset, yBorder+ir*height+layerYOffset);
                        theTiles[index]->setZValue(depth(il,ic,ir));
                        index++;
                        break;
                    case isYOffsetTile:
                        mLayout[il][ic][ir] = index;
                        theTiles[index]->setColumn(ic);
                        theTiles[index]->setRow(ir);
                        theTiles[index]->setLayer(il);
                        theTiles[index]->setYOffset(static_cast<int>(-height/2.));
                        theTiles[index]->setPos(ic*width+layerXOffset+xBorder, static_cast<int>(ir*height-height/2.)+layerYOffset+yBorder);
                        theTiles[index]->setZValue(depth(il,ic,ir));
                        index++;
                        break;
                    case isXYOffsetTile:
                        mLayout[il][ic][ir] = index;
                        theTiles[index]->setColumn(ic);
                        theTiles[index]->setRow(ir);
                        theTiles[index]->setLayer(il);
                        theTiles[index]->setXOffset(static_cast<int>(width/2.));
                        theTiles[index]->setYOffset(static_cast<int>(-height/2.));
                        theTiles[index]->setPos(static_cast<int>(ic*width+width/2.)+layerXOffset+xBorder, static_cast<int>(ir*height-height/2.)+layerYOffset+yBorder);
                        theTiles[index]->setZValue(depth(il,ic,ir));
                        //assert(theTiles[index]->index() == index); // just for now
                        index++;
                        break;
                    case isEmpty:
                    default:
                        mLayout[il][ic][ir] = -1;
                        break;
                }
            }
        }
    }
    return QSize(mColumns*width+2*xBorder, mRows*height+2*yBorder);
}
 
int smTileLayout::depth(int l, int c, int r) const
{
    return 70000-(6-l)*10000-(mColumns-c)*100+r;
}
 
//int smTileLayout::depth(int l, int c, int r) const
//{
//    return l*10000+(mColumns-c)*100+r;
//}
 
void smTileLayout::removeTile(const smTileItem* tile)
{
    if (tile)
        mLayout[tile->layer()][tile->column()][tile->row()] = -1;
}
 
void smTileLayout::undoRemoveTile(const smTileItem* tile)
{
    if (tile)
        mLayout[tile->layer()][tile->column()][tile->row()] = tile->index();
}
 
bool smTileLayout::isAtBorder(const smTileItem* tile) const
{
    if (!tile) return false;
    int ir = tile->row();
    int ic = tile->column();
    int il = tile->layer();
    if (mTopoLayout[il][ic][ir] < 0) return false;
     
    //
    //  start with same layer
    //
    bool sameLeft = false; // is tile on the left
    if (ic > 0) {
        if (mLayout[il][ic-1][ir] >= 0) sameLeft = true;
        if (ir < mRows-1 &&
            ((mLayout[il][ic-1][ir+1] >= 0 && mTopoLayout[il][ic-1][ir+1] == isYOffsetTile) ||
             (mLayout[il][ic-1][ir+1] >= 0 && mTopoLayout[il][ic-1][ir+1] == isXYOffsetTile))) sameLeft = true;
    }
     
    bool sameRight = false; // is tile on the right
    if (ic < mColumns-1) {
        if (mLayout[il][ic+1][ir] >= 0) sameRight = true;
        if (ir < mRows-1 && mLayout[il][ic+1][ir+1] >= 0 &&
          mTopoLayout[il][ic+1][ir+1] == isYOffsetTile) sameRight = true;
    }
     
    bool sameOK = !(sameRight && sameLeft);
     
    //
    //  next layer up
    //
    if (il == mNumberOfLayers-1) return sameOK;  // is already top layer
    il++;
    bool nextLayer = false;
    if (mLayout[il][ic][ir] >= 0) nextLayer = true;
    if (ic > 0) {
        if ((mLayout[il][ic-1][ir] >= 0 && mTopoLayout[il][ic-1][ir] == isXOffsetTile) ||
            (mLayout[il][ic-1][ir] >= 0 && mTopoLayout[il][ic-1][ir] == isXYOffsetTile) ) nextLayer = true;
        if (ir < mRows-1 &&
            ((mLayout[il][ic-1][ir+1] >= 0 && mTopoLayout[il][ic-1][ir+1] == isXYOffsetTile) ||
             (mLayout[il][ic][ir+1] >= 0 && mTopoLayout[il][ic][ir+1] == isYOffsetTile) ||
             (mLayout[il][ic][ir+1] >= 0 && mTopoLayout[il][ic][ir+1] == isXYOffsetTile) )) nextLayer = true;
    }
     
    return (sameOK && !nextLayer);
}
 
